using System.Collections.Generic;
using System.Net;
using System;
using SpoonCore2;
using SpoonCore2.Client;
using SpoonCore2.OverlayNetwork.Pastry;
using SpoonCore2.OverlayNetwork.Pastry.Messages;
using System.Threading;
using SpoonCore2.Routing;
using SpoonCore2.Tools;

namespace SpoonCore2.Routing
{
	public class RoutingTable : ITable
	{
		private readonly SortedList<byte, SortedList<byte, ClientInfo>> _table = new SortedList<byte, SortedList<byte, ClientInfo>>();
		private int _addCount;
		private readonly Random _rand = new Random();
		private readonly PastryNetwork _network;

		public RoutingTable(PastryNetwork network)
		{
			_network = network;
		}

		public IList<ClientInfo> NodesFor(ClientInfo client)
		{
			byte level = client.Id.LevelTo(_network.ClientInfo.Id);

			Monitor.Enter(_table);
			IList<ClientInfo> res;
			if (_table.ContainsKey(level)) res = _table[level].Values;
			else res = new List<ClientInfo>();
			Monitor.Exit(_table);
			return res;
		}

		public void Add(ClientInfo info)
		{
			//ColorConsole.WriteLine(_client.Id + ": Insert to routingtable " + info.Id);
			byte level = info.Id.LevelTo(_network.ClientInfo.Id);
			Monitor.Enter(_table);
			if (level < info.Id.Count)
			{
				byte value = info.Id.ValueAt(level);
				if (value != _network.ClientInfo.Id.ValueAt(level))
				{
					if (!_table.ContainsKey(level)) _table.Add(level, new SortedList<byte, ClientInfo>());
					if (!_table[level].ContainsKey(value))
					{
						_table[level].Add(value, info);
						_addCount++;
					}
					else if (_table[level][value] == info) _table[level][value].AddEndpoint(info.Endpoints);
				}
			}
			Monitor.Exit(_table);
		}

		public ClientInfo FindNext(SpoonIdentifier id)
		{
			byte level = id.LevelTo(_network.ClientInfo.Id);
			ClientInfo res = null;

			Monitor.Enter(_table);
			if (_table.ContainsKey(level))
			{
				if (_table[level].ContainsKey(id.ValueAt(level)))
					res = _table[level][id.ValueAt(level)];
				else
				{
					byte dist = (byte)(_network.ClientInfo.Id.ValueAt(level) - id.ValueAt(level)); // Self distance
					foreach (ClientInfo info in _table[level].Values)
					{
						if ((byte)(info.Id.ValueAt(level) - id.ValueAt(level)) < dist)
						{
							res = info;
							dist = (byte)(info.Id.ValueAt(level) - id.ValueAt(level));
						}
					}
				}
			}
			Monitor.Exit(_table);

			return res;
		}

		public IList<ClientInfo> Clients
		{
			get
			{
				List<ClientInfo> res = new List<ClientInfo>();
				Monitor.Enter(_table);
				foreach (SortedList<byte, ClientInfo> item in _table.Values)
				{
					res.AddRange(item.Values);
				}
				Monitor.Exit(_table);
				return res;
			}
		}

		public IList<ClientInfo> FindByMask(List<SearchMask> list)
		{

			List<ClientInfo> res = new List<ClientInfo>();
			Monitor.Enter(_table);
			foreach (SearchMask mask in list)
			{
				if (_table.ContainsKey(mask.Level))
				{
					for (byte x = 0; x < mask.Count; x++)
					{
						if (mask[x] && _table[mask.Level].ContainsKey(x))
						{
							res.Add(_table[mask.Level][x]);
							mask[x] = false;
						}
					}
				}
			}
			Monitor.Exit(_table);
			return res;
		}

		public void Refresh()
		{
			byte maxLevel = 0;
			byte minLevel = byte.MaxValue;

			// fill empty entrys
			Monitor.Enter(_table);
			for (int y = 0; y < _table.Keys.Count; y++)
			{
				byte level = _table.Keys[y];
				if (level > maxLevel) maxLevel = level;
				if (level < minLevel) minLevel = level;

				SearchMask item = new SearchMask(level);

				for (byte x = 0; x < item.Count; x++)
				{
					if (_network.ClientInfo.Id.ValueAt(level) != x) item[x] = !_table[level].ContainsKey(x);
				}

				ExchangeMessage msg = new ExchangeMessage();
				msg.SearchList.Add(item);

				if (_table[level].Count > 0 && item.Set) _table[level].Values[_rand.Next(_table[level].Count)].Send(msg);
			}

			// fill empty layers, send search to lower level node, plus search for lower level
			for (byte level = 0; level <= maxLevel; level++)
			{
				if (!_table.ContainsKey(level))
				{
					ExchangeMessage findMsg = new ExchangeMessage();
					byte sendLevel = maxLevel;
					if (level >= minLevel && level <= maxLevel)
					{
						foreach (byte key in _table.Keys)
						{
							if (key < sendLevel && key > level) sendLevel = key;
						}
					}
					else if (level < minLevel) sendLevel = minLevel;
					else sendLevel = maxLevel;


					SearchMask mask = new SearchMask(level);
					mask.SetAll();
					findMsg.SearchList.Add(mask);

					if (_table.ContainsKey(sendLevel))
					{
						ClientInfo client = _table[sendLevel].Values[_rand.Next(_table[sendLevel].Count)];
						client.Send(findMsg);
					}
				}
			}
			Monitor.Exit(_table);
		}

		public void Remove(ClientInfo client)
		{
			byte level = client.Id.LevelTo(_network.ClientInfo.Id);
			if (level < client.Id.Count)
			{
				byte value = client.Id.ValueAt(level);

				Monitor.Enter(_table);
				if (_table.ContainsKey(level))
				{
					if (_table[level].ContainsKey(value)) _table[level].Remove(value);
					if (_table[level].Count == 0) _table.Remove(level);
				}
				Monitor.Exit(_table);
			}
		}

		public bool Check(List<ClientInfo> clients)
		{
			bool status = true;
			Monitor.Enter(_table);
			foreach (ClientInfo client in clients)
			{
				byte level = client.Id.LevelTo(_network.ClientInfo.Id);
				if (level < client.Id.Count)
				{
					byte value = client.Id.ValueAt(level);

					if (_table.ContainsKey(level) && !_table[level].ContainsKey(value) || !_table.ContainsKey(level))
					{
						ColorConsole.WriteLine(_network.ClientInfo + " (RoutingTable): not found " + client.Id + ", addCount: " + _addCount);
						status = false;
						break;
					}
				}
			}
			Monitor.Exit(_table);
			return status;
		}
	}
}